package com.qianlou.controller;


import com.baidu.aip.speech.AipSpeech;
import com.qianlou.common.enums.ResponseStatusEnum;
import com.qianlou.common.result.BaseResponse;
import com.qianlou.config.BaiduSdkProperties;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;

@Slf4j
@RestController
@RequestMapping("/speech")
public class BaiduSpeechController {

    @Resource
    private BaiduSdkProperties  baiduSdkProperties;

    /**
     * 上传语音文件接口
     * 该方法接收一个multipart文件作为输入，将其转换为PCM格式，并调用基本数据处理方法进行处理
     * 主要用于语音识别或处理服务
     *
     * @param file 用户上传的语音文件，期望是一个MP3文件
     * @return 返回一个BaseResponse对象，包含处理结果或错误信息
     */
    @PostMapping("/uploadVoice")
    public BaseResponse uploadVoice(@RequestParam("file") MultipartFile file){
        // 获取文件原始名称
        String filename = file.getOriginalFilename();
        // 检查文件名是否为空，如果为空则返回错误响应
        if (StringUtils.isBlank(filename)) {
            return BaseResponse.errorCustom(ResponseStatusEnum.FILE_UPLOAD_NULL_ERROR);
        }
        try {
            // 将上传的MP3文件转换为PCM格式字节数组
            byte[] pcmByteData = mp3ConvertToPcm(file.getInputStream());
            // 调用基本数据处理方法，传入PCM字节数组和格式信息，以及处理选项
            JSONObject response = basicData(pcmByteData, "pcm", getOptions());
            // 从响应中获取结果数组
            JSONArray resultArr = response.getJSONArray("result");
            // 获取结果数组中的第一个元素作为处理结果
            String result = resultArr.get(0).toString();
            // 记录处理结果
            log.info("识别结果：{}", result);
            // 返回包含处理结果的成功响应
            return BaseResponse.ok(result);
        } catch (Exception ex) {
            log.error("uploadVoice error: ", ex);
        }
        return BaseResponse.ok();
    }

    /**
     * 将MP3音频流转换为PCM格式的字节流
     * 此方法读取一个包含MP3音频的输入流，并将其转换为PCM格式的音频数据
     * 主要用于音频处理或转换的场景
     *
     * @param inputStream 包含MP3音频的输入流，不能为null
     * @return PCM格式的音频数据字节流
     * @throws IllegalArgumentException 如果输入流为null，则抛出此异常
     * @throws RuntimeException         如果MP3到PCM的转换过程中发生错误，则抛出此异常
     */
    public static byte[] mp3ConvertToPcm(InputStream inputStream) {
        // 检查输入流是否为null，确保方法的输入有效性
        if (inputStream == null) {
            throw new IllegalArgumentException("Input stream cannot be null.");
        }
        // 尝试使用输入流获取音频输入流，此过程可能抛出UnsupportedAudioFileException异常
        try (AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(inputStream)) {
            // 获取源音频的格式信息
            AudioFormat sourceFormat = audioInputStream.getFormat();
            // 定义目标PCM格式
            AudioFormat targetFormat = new AudioFormat(
                    AudioFormat.Encoding.PCM_SIGNED,
                    sourceFormat.getSampleRate(),
                    16,
                    sourceFormat.getChannels(),
                    sourceFormat.getChannels() * 2,
                    sourceFormat.getSampleRate(),
                    false);

            // 将源音频流转换为目标PCM格式的音频流
            try (AudioInputStream convertedStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream)) {
                // 使用ByteArrayOutputStream收集转换后的PCM数据
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                byte[] buffer = new byte[4096];
                int bytesRead;
                // 循环读取转换后的音频数据并写入ByteArrayOutputStream
                while ((bytesRead = convertedStream.read(buffer)) != -1) {
                    byteArrayOutputStream.write(buffer, 0, bytesRead);
                }
                // 返回PCM格式的音频数据字节流
                return byteArrayOutputStream.toByteArray();
            }
        } catch (UnsupportedAudioFileException | IOException e) {
            // 记录日志或封装成运行时异常抛出，以便调用者可以处理异常
            throw new RuntimeException("Failed to convert MP3 to PCM", e);
        }
    }

    /**
     * 获取识别参数
     * 1537	普通话(纯中文识别)	    语音近场识别模型	有标点	支持自定义词库
     * 1737	英语	                英语模型	        无标点	不支持自定义词库
     * 1637	粤语	                粤语模型	        有标点	不支持自定义词库
     * 1837	四川话	            四川话模型	    有标点	不支持自定义词库
     */
    private static HashMap<String, Object> getOptions() {
        HashMap<String, Object> options = new HashMap<>();
        options.put("dev_pid", 1537);
        return options;
    }

    // 语音识别（来自文件）

    /**
     * 使用百度AI平台的语音识别功能处理语音数据
     * 此方法主要负责将传入的语音数据转换为可识别的文本格式
     * 它通过调用AipSpeech客户端的asr方法来实现语音识别
     *
     * @param voiceData 语音数据，以字节数组形式提供，是进行语音识别的原始输入
     * @param fileType  语音文件的类型，用于告知语音识别引擎采用何种格式进行处理
     * @param options   包含识别参数的哈希表，用于定制化识别过程中的特定需求，如语言选择、词汇表等
     * @return 返回一个JSONObject对象，包含了识别结果和可能的识别过程中的额外信息
     */
    public  JSONObject basicData(byte[] voiceData, String fileType, HashMap<String, Object> options) {
        // 获取已初始化的AipSpeech客户端实例
        AipSpeech client = getClient();
        // 调用AipSpeech客户端的asr方法进行语音识别
        return client.asr(voiceData, fileType, 16000, options);
    }

    /**
     * 获取百度AI语音识别客户端
     * <p>
     * 该方法初始化并返回一个AipSpeech对象，用于与百度AI平台进行语音相关的操作
     * 包括语音识别、语音合成等
     *
     * @return AipSpeech实例，用于执行语音相关操作
     */
    public  AipSpeech getClient() {
        // 创建AipSpeech实例，需要传入百度AI平台的APP_ID、API_KEY、SECRET_KEY作为认证信息
        AipSpeech client = new AipSpeech(
                baiduSdkProperties.getAppId(),
                baiduSdkProperties.getApiKey(),
                baiduSdkProperties.getSecretKey());

        // 可选：设置网络连接参数
        // 设置连接超时时间，单位为毫秒，默认2000毫秒
        client.setConnectionTimeoutInMillis(2000);
        // 设置读取数据超时时间，单位为毫秒，默认60000毫秒
        client.setSocketTimeoutInMillis(60000);

        // 返回初始化完毕的AipSpeech实例
        return client;
    }


}
